﻿using UFrame.ECS;
using UnityEngine;

namespace UFrame
{
    /// <summary>
    /// 第一人称控制
    /// 1. 平滑移动
    /// 2. 立即反应移动
    /// </summary>
    public class PlayerControl : MonoBehaviour
    {
        [SerializeField, Range(0f, 500f)] public float maxSpeed = 10f;
        [SerializeField, Range(0f, 500f)] public float shiftSpeed = 10f;
        [SerializeField, Range(0f, 100f)] public float maxAcceleration = 10f;
        [SerializeField, Range(0f, 10f)] public float jumpHeight = 2f;
        [SerializeField, Range(0, 5)] public int maxAirJumps = 0;
        [SerializeField, Range(0, 90)] public float maxGroundAngle = 25f;

        public bool isSubSpeed = false;
        public bool isDebug = false;
        private Vector3 velocity;
        private Vector3 desiredVelocity;
        private Vector2 playerInput;
        private Vector2 playerRawInput;
        private Vector3 contactNormal;

        private bool desiredJump;

        //private bool onGround;
        public Rigidbody body;
        public BoxCollider collision;
        private int jumpPhase;

        private int groundContactCount; //计算挨着地面的次数

        public bool OnGround => groundContactCount > 0;
        private float minGroundDotProduct;
        

        //rotate args
        public float turnSmotthVelocity = 2f;
        private float smoothRotateTime = 0.5f;
        public Transform playerCamera;
        private Vector3 moveDir;

        public void Awake()
        {
            collision = GetComponent<BoxCollider>();
            OnValidate();
            if (body == null)
                body = GetComponent<Rigidbody>();
            
            
        }

        public void Update()
        {
            SmoothMoveUpdate();
            ControlRotate();
        }

        public void FixedUpdate()
        {
            SmoothMoveFixed();
        }

        private void OnValidate()
        {
            minGroundDotProduct = Mathf.Cos(maxGroundAngle * Mathf.Deg2Rad);
        }

        public void SmoothMoveUpdate()
        {
            playerInput.x = Input.GetAxis("Horizontal");
            playerInput.y = Input.GetAxis("Vertical");
            bool isAddSpeed = Input.GetKey(KeyCode.LeftShift);
            playerInput = Vector2.ClampMagnitude(playerInput, 1f);

            if(isAddSpeed)
                desiredVelocity = new Vector3(playerInput.x, 0f, playerInput.y) * (maxSpeed + shiftSpeed);
            else 
                desiredVelocity = new Vector3(playerInput.x, 0f, playerInput.y) * maxSpeed;

            desiredJump |= Input.GetKeyDown(KeyCode.Space);

   
            ControlRotate();
        }

        private void ControlRotate()
        {
            playerRawInput.x = Input.GetAxisRaw("Horizontal");
            playerRawInput.y = Input.GetAxisRaw("Vertical");
            var direction = new Vector3(playerRawInput.x,0,playerRawInput.y).normalized;
            if(direction.magnitude > 0.1f)
            {
                float targetAngle = Mathf.Atan2(direction.x,direction.z) * Mathf.Rad2Deg;

                if(playerCamera != null)
                {
                    targetAngle += playerCamera.eulerAngles.y;
                }

                float angle = Mathf.SmoothDampAngle(transform.eulerAngles.y,targetAngle,
                                ref turnSmotthVelocity,smoothRotateTime);
                
                moveDir = Quaternion.Euler(0,targetAngle,0) * Vector3.forward;
                transform.rotation = Quaternion.Euler(0,angle,0);

            }
        }

        public void SmoothMoveFixed()
        {
            UpdateState();
            AdjustVelocity();

            if (desiredJump)
            {
                desiredJump = false;
                Jump();
            }

            
            body.velocity = velocity;
            body.angularVelocity = new Vector3(0,0,0);
            ClearState();
        }

        private void AdjustVelocity()
        {
            Vector3 xAxis = ProjectOnContactPlane(Vector3.right).normalized;
            Vector3 zAxis = ProjectOnContactPlane(Vector3.forward).normalized;

            float currentX = Vector3.Dot(velocity, xAxis);
            float currentZ = Vector3.Dot(velocity, zAxis);

            velocity = body.velocity;                       //如果Veloctiy太小，世界重力会导致人物单独一个键无法移动
            float maxSpeedChange = maxAcceleration * Time.fixedDeltaTime;

            float newX =
                Mathf.MoveTowards(currentX, desiredVelocity.x, maxSpeedChange);
            float newZ =
                Mathf.MoveTowards(currentZ, desiredVelocity.z, maxSpeedChange);

            velocity += xAxis * (newX - currentX) + zAxis * (newZ - currentZ);

            
            if (isDebug)
            {
                PLGUITools.ShowUI("desiredVelocity" ,desiredVelocity);
                PLGUITools.ShowUI("velocity" ,velocity);
            }
        }

        private void UpdateState()
        {
            if (body != null)
                velocity = body.velocity;

            if (OnGround)
            {
                jumpPhase = 0;
                if (groundContactCount > 1)
                {
                    contactNormal.Normalize();
                }
            }
            else
            {
                contactNormal = Vector3.up;
            }
        }

        public void ClearState()
        {
            groundContactCount = 0;
            contactNormal = Vector3.zero;
        }

        public void Jump()
        {
            if (OnGround || jumpPhase < maxAirJumps)
            {
                //这里是有一个推理公式的-2f * Physics.gravity.y * jumpHeight
                jumpPhase += 1;
                float jumpSpeed = Mathf.Sqrt(-2f * Physics.gravity.y * jumpHeight);

                //接触面向量 Dot 速度 > 0  面对和背对
                float alignedSpeed = Vector3.Dot(velocity, contactNormal);
                if (alignedSpeed > 0f)
                {
                    jumpSpeed = Mathf.Max(jumpSpeed - alignedSpeed, 0f);
                }

                velocity += contactNormal * jumpSpeed;
            }
        }

        public void OnCollisionStay(Collision collision)
        {
            EvaluateCollision(collision);
        }

        public void EvaluateCollision(Collision collision)
        {
            for (int i = 0; i < collision.contactCount; i++)
            {
                Vector3 normal = collision.GetContact(i).normal;
                if (normal.y >= minGroundDotProduct)
                {
                    groundContactCount += 1;
                    contactNormal += normal;
                }
            }
        }

        public Vector3 ProjectOnContactPlane(Vector3 vector)
        {
            return vector - contactNormal * Vector3.Dot(vector, contactNormal);
        }

        #region 另外一种移动方式

        public void ImmediatelyMoveSus()
        {
            if (isSubSpeed)
            {
                if (Input.GetKeyUp(KeyCode.A) || Input.GetKeyUp(KeyCode.D))
                {
                    playerInput.x = 0;
                    velocity.x = 0;
                }

                if (Input.GetKeyUp(KeyCode.W) || Input.GetKeyUp(KeyCode.S))
                {
                    playerInput.y = 0;
                    velocity.z = 0;
                }
            }
        }

        #endregion
    }
}